﻿/***************************************************************************
 * 文件名：ActionInvokeUtil
 * 功能：委托执行工具类
 * 说明：
 * -------------------------------------------------------------------------
 * 创建时间：2023/11/11 11:12:20
 * 创建人：吴承来
 * 邮箱： lailaiwcl@163.com
 * =========================================================================
 *
 * 修改人：   
 * 修改时间：   
 * 修改说明：   
 ***************************************************************************/
using EF.Log;
using System;
using System.Threading;

namespace EF.Basic.Util
{
    /// <summary>
    /// 委托执行工具类
    /// </summary>
    public class ActionInvokeUtil
    {

        private ActionInvokeUtil()
        {

        }

        //等待处理信号事件
        private static ManualResetEvent _WaitHandle = new ManualResetEvent(false);

        /// <summary>
        /// 执行方法委托
        /// </summary>
        /// <param name="action">需要执行的委托方法</param>
        /// <param name="actionName">执行的委托方法名称</param>
        /// <param name="retryTimeIntervalSeconds">重试等待时间间隔，单位秒</param>
        /// <param name="retryTimes">重试次数,如果该值小于等于0则会一直重试,直到成功</param>
        /// <param name="isNoThrowException">对外不抛出异常，false 发生了异常该方法会抛出异常，true 异常发生后该方法只会记录日志，不会抛出异常</param>
        /// <param name="exceptionCallBack">异常钩子，方法执行异常后如果该参数不为空则会执行该方法.该委托两个参数，第一个参数为异常信息，第二个参数为重试的次数</param>
        /// <param name="isAsyn">是否异步 false 同步执行 true 异步执行</param>
        /// <exception cref="ArgumentNullException">参数异常</exception>
        public static void Invoke(Action action, string actionName, int retryTimeIntervalSeconds, int retryTimes = 0, bool isNoThrowException = false, Action<Exception, int> exceptionCallBack = null, bool isAsyn = false)
        {
            #region 参数校验
            if (action == null)
            {
                var returnCode = ArgumentCheckException(isNoThrowException, "参数错误：委托方法不能为空", exceptionCallBack);
                if (returnCode == -1)
                {
                    return;
                }
            }
            if (string.IsNullOrWhiteSpace(actionName))
            {
                var returnCode = ArgumentCheckException(isNoThrowException, "参数错误：委托方法名称不能为空", exceptionCallBack);
                if (returnCode == -1)
                {
                    return;
                }
            }
            if (retryTimeIntervalSeconds <= 0)
            {
                var returnCode = ArgumentCheckException(isNoThrowException, "参数错误：重试等待时间间隔[retryTimeIntervalSeconds]参数必须大于0", exceptionCallBack);
                if (returnCode == -1)
                {
                    return;
                }
            }
            #endregion

            if (retryTimes <= 0)
            {
                retryTimes = int.MaxValue;
            }

            if (isAsyn) //异步
            {
                Logger.Trace(TraceLevel.Info, $"委托方法[{actionName}]开始异步执行...");
                EF.Threading.AsyncTask.StartNew(() => //考虑到上下文问题，这里只能使用系统框架提供的异步执行方法
                {
                    try
                    {
                        DoWork(action, actionName, retryTimeIntervalSeconds, retryTimes, isNoThrowException, exceptionCallBack);
                    }
                    catch
                    {
                    }
                });
            }
            else //同步
            {
                Logger.Trace(TraceLevel.Info, $"委托方法[{actionName}]开始同步执行...");
                try
                {
                    DoWork(action, actionName, retryTimeIntervalSeconds, retryTimes, isNoThrowException, exceptionCallBack);
                }
                catch (Exception ex)
                {
                    if (!isNoThrowException)
                    {
                        throw ex;
                    }
                }

            }
        }

        /// <summary>
        /// 终止执行
        /// </summary>
        public static void Abort()
        {
            if (_WaitHandle != null)
            {
                _WaitHandle.Reset();
                _WaitHandle.Dispose();
            }
            _WaitHandle = new ManualResetEvent(false);
        }

        private static void DoWork(Action action, string actionName, int retryTimeIntervalSeconds, int retryTimes, bool isNoThrowException, Action<Exception, int> exceptionCallBack)
        {
            var executeTimes = 0;
            while (executeTimes++ <= retryTimes)
            {
                try
                {
                    action.Invoke();
                    Logger.Trace(TraceLevel.Info, $"委托方法[{actionName}]执行成功");
                    return;
                }
                catch (Exception ex)
                {
                    if (exceptionCallBack != null)
                    {
                        try
                        {
                            exceptionCallBack.Invoke(ex, executeTimes);
                        }
                        catch
                        {
                        }
                    }
                    Logger.Trace(TraceLevel.Error, $"委托方法[{actionName}]执行失败[{executeTimes}/{retryTimes}],等待[{retryTimeIntervalSeconds}]秒后将继续执行：{ex.Message}");
                    if (!isNoThrowException && executeTimes >= retryTimes)
                    {
                        throw new Exception($"委托方法执行错误：{ex.Message}");
                    }
                    var isStop = _WaitHandle.WaitOne(retryTimeIntervalSeconds * 1000);
                    if (isStop)
                    {
                        return;
                    }
                    continue;
                }
            }
        }


        private static int ArgumentCheckException(bool isNoThrowException, string errorMsg, Action<Exception, int> exceptionCallBack)
        {
            if (isNoThrowException)
            {
                Logger.Trace(TraceLevel.Error, errorMsg);
                if (exceptionCallBack != null)
                {
                    exceptionCallBack.Invoke(new ArgumentException(errorMsg), 0);
                }
                return -1;
            }
            throw new ArgumentException(errorMsg);
        }
    }
}
